<!--
 * @Author: Betty
 * @Date: 2021-01-09 23:34:11
 * @LastEditors: Betty
 * @LastEditTime: 2021-02-08 21:13:58
 * @Description: 上传组件
-->
<template>
  <div class="file-uploaded">
    <!-- 这是让人点击的按钮 -->
    <div @click.prevent="triggerUpload" class="file-upload-container" v-bind="$attrs">
      <!-- 删除已上传图片的按钮 -->
      <button class="btn btn-danger delete-btn" v-if="fileStatus === 'success'" @click.prevent.stop="remove">删除</button>
      <!-- 对正在上在传时、上传成功和失败分别使用不同的命名插槽 -->
      <slot name="loading" v-if="fileStatus === 'loading'">
        <button class="btn btn-primary" disabled>上传中……</button>
      </slot>
      <slot name="uploaded" v-else-if="fileStatus === 'success'" :uploadedData="uploadedData">
       <button  class="btn">上传成功</button>
      </slot>
      <slot name="default" v-else >
        <button class="btn btn-primary">点击上传文件</button>
      </slot>
    </div>
    <!-- 这是上传文件的input -->
    <input type="file" class="file-input d-none" ref="fileInput" @change="handleFileChanged" />
  </div>
</template>

<script lang='ts'>
import { defineComponent, PropType, ref, watch } from 'vue'
import axios from 'axios'
// 定义组件的上传状态类型
type UploadStatus = 'ready' | 'loading' | 'success' | 'error'
// 定义beforeUpload方法的类型，也就是返回布尔值
// eslint-disable-next-line no-unused-vars
type CheckFunction = (file: File) => boolean
export default defineComponent({
  name: '',
  props: {
    // 指定发post请求的URL，字符串，必填
    action: {
      type: String,
      required: true
    },
    // 指定上传之前的检查函数
    beforeUpload: {
      type: Function as PropType<CheckFunction>
    },
    // 新增一个属性，用来回填上传后的图片
    uploaded: {
      type: Object
    }
  },
  emits: ['file-uploaded', 'file-uploaded-error','file-removed'],
  // 不让根元素继承加到元素上的属性
  inheritAttrs: false,
  setup(props, context) {
    // 拿到那个input元素
    const fileInput = ref<null | HTMLInputElement>(null)
    // 输出回填的数据来看看，假如是同步传过来的，我们就可以直接修改
    // 异步传过来的，我们就只能用watch
    console.log(props.uploaded)
    // 定义上传状态的ref
    // 假如曾经上传过的东西回填过来了，那么认为上传已经成功了，否则就是默认的ready状态
    const fileStatus = ref<UploadStatus>(props.uploaded ? 'success' : 'ready')
    // 来拿到上传的数据
    // 假如曾经上传过的东西回填过来了，那么它就是目前已经上传好的图片，否则是空的
    const uploadedData = ref(props.uploaded)
    // 来监听一个props，注意props属性是普通属性，要包装成getter函数才可以监听
    watch(() =>  props.uploaded, (newValue) => {
      // 如果拿到了回填的值，那么我们来手动更新这些ref的值
      if (newValue) {
        fileStatus.value = 'success'
        uploadedData.value = newValue
      }
    })
    // 上传触发的点击事件函数
    const triggerUpload = () => {
      if (fileInput.value) {
        fileInput.value.click()
      }
    }
    // 点击删除按钮
    const remove = () => {
      // 删除uploadedData的数据
      uploadedData.value = undefined
      // 让组件状态恢复成未上传
      fileStatus.value = 'ready'
      // 通知父组件，图片已经删除了
      context.emit('file-removed')
    }
    // onchange事件的处理函数
    const handleFileChanged = (e: Event) => {
      // 拿到input元素
      const curretntTarget = e.target as HTMLInputElement
      // 从files拿到里面的文件
      // 如果拿到文件，那么就开始上传了
      if (curretntTarget.files) {
        // 把files转成array
        const files = Array.from(curretntTarget.files)
        // 在上传之前，调用beforeUpload函数，如果发现没有通过，那么就return
        // 通过和没有验证函数的，都会往下执行
        if (props.beforeUpload) {
          const result = props.beforeUpload(files[0])
          if (!result) return
        }
        // 开始上传了，所以让文件状态为"loading"
        fileStatus.value = 'loading'
        // 使用formData对象来提交文件数据
        const formData = new FormData()
        formData.append('file', files[0])
        // 发送post请求，上传文件
        axios
          .post(props.action, formData, {
            headers: {
              'Content-Type': 'multipart/form-data'
            }
          })
          .then((resp) => {
            // 成功了，就把状态设为success
            fileStatus.value = 'success'
            // 触发成功的事件，传递返回的结果
            context.emit('file-uploaded', resp.data)
            // 把结果赋值给uploadedData
            uploadedData.value = resp.data
            console.log(resp.data)
          })
          .catch((error) => {
            // 失败了，就把状态设为error
            fileStatus.value = 'error'
            // 触发失败的事件
            context.emit('file-uploaded-error', { error })
            console.log(error)
          })
          .finally(() => {
            if (fileInput.value) {
              // 不管成功还是失败，最后都要把状态还原
              // 把上传文件input的值变回空
              fileInput.value.value = ''
            }
          })
      }
    }
    return {
      fileInput,
      triggerUpload,
      fileStatus,
      handleFileChanged,
      uploadedData,
      remove
    }
  }
})
</script>

<style>
  .file-uploaded {
    position: relative;
  }

  .delete-btn {
    position: absolute;
    top: 0;
    right: 0;
  }
</style>
